/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.blocks;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import edu.cmu.ri.createlab.jj.JubJub.EmptyStringException;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.*;
import edu.cmu.ri.createlab.jj.visitors.*;
import edu.cmu.ri.createlab.jj.projects.*;

public class VarBlock implements BlockInterface
{
   
   /**
    * \defgroup BlockVars These variables are common to all BlockInterface types.
    */
   /*@{*/

   ///The name given to this block. CodeBlock and BlockBlock types should have unique names
   private String name;
   ///The JJClass which these blocks belong to and where they will be written to
   ///when output as code/xml
   private JJClass classOwner;
   ///What type of block this is from the list of BlockTypes
   private BlockTypes type;
   ///A unique ID for this block
   private int id;
   ///A list of RefBlock instances that reference this block.
   private List<RefBlock> refs;
   ///Whether this block is still valid or not
   private boolean valid;
   ///comment for this block
   private String comment;
   
   /*@}*/

   ///The type of variable, defined in BlockDefs
   private VarTypes varType;
   ///The length, 1 by default, if >1 this creates an array
   private int length;
   ///Whether or not this variable is a literal (a number/string/boolean
   ///instead of a variable that stores something).
   private boolean literal;
   ///A String representation of the value the variable is being initialized to
   private String[] value;
   /**
    * \defgroup CallableVars These variables are common to CodeBlock, BlockBlock, VarBlock, and ObjectBlock
    */
   /*@{*/
   ///Access keywords, same as in CodeBlock and BlockBlock classes, defined in BlockDefs
   private AccessTypes[] access;
   ///This is to provide easy boolean access to the access types for quick lookup.
   ///The order is PUBLIC, PRIVATE, PROTECTED, FINAL, STATIC and should be referenced
   ///by calling BlockDefs.AccessTypes.TYPE.getIndex
   private boolean[] isAccess = {false, false, false, false, false};
   ///The name used when calling/defining this block in code.
   private String callName;
   /*@}*/
   
   ///This is so that objects can be declared just like variables by linking
   ///a VariableBlock to an ObjectBlock.
   private ObjectBlock object;
   ///This lets you use a variable as an Object literal for Strings
   private String objectType;
   
   /**
    * @param name A unique variable name for this block.
    * @param owner Which JJClass it belongs to
    * @param ID A globally unique identifier
    * @param vType The type of variable this is
    * @throws EmptyStringException
    * 
    * Create a new VarBlock which, if literal is set to true, gets used as a literal
    * value instead of a variable. The IDE should handle creating literal vars as
    * needed when users fill in spaces.
    */
   public VarBlock(String name, JJClass owner, int ID, VarTypes vType, boolean literal) 
      throws Exception
   {
      setName(name);
      this.setOwner(owner);
      this.id = ID;
      this.type = BlockTypes.VAR;
      this.varType = vType;
      this.access = null;
      this.refs = new LinkedList<RefBlock>();
      this.length = 1;
      this.object = null;
      this.literal = literal;
      this.valid = true;
      this.value = new String[1];
      this.setDefaultValue();
      this.comment = "";
   }
   
   /**
    * @param name A unique variable name for this block.
    * @param owner Which JJClass it belongs to
    * @param ID A globally unique identifier
    * @param vType The type of variable this is
    * @throws EmptyStringException
    * 
    * Create a new VarBlock. Everything except the AccessTypes must be specified
    * on creation.
    */
   public VarBlock(String name, JJClass owner, int ID, VarTypes vType) 
      throws Exception
   {
      setName(name);
      //TODO Fix owner code to make an owner optional for variables
      this.setOwner(owner);
      this.id = ID;
      this.type = BlockTypes.VAR;
      this.varType = vType;
      this.access = null;
      this.refs = new LinkedList<RefBlock>();
      this.length = 1;
      this.object = null;
      this.literal = false;
      this.valid = true;
      this.value = new String[1];
      this.setDefaultValue();
      this.comment = "";
   }
   
   /**
    * @param name A unique variable name for this block.
    * @param owner Which JJClass it belongs to
    * @param ID A globally unique identifier
    * @param vType The type of variable this is
    * @param length How long it should be, array if >1
    * @throws EmptyStringException
    * 
    * Create a new VarBlock. Everything except the AccessTypes must be specified
    * on creation.
    */
   public VarBlock(String name, JJClass owner, int ID, VarTypes vType, 
         AccessTypes[] access) 
      throws Exception
   {
      this.setName(name);
      this.setOwner(owner);
      this.id = ID;
      this.type = BlockTypes.VAR;
      this.varType = vType;
      this.setAccess(access);
      this.refs = new LinkedList<RefBlock>();
      this.length = 1;
      this.object = null;
      this.literal = false;
      this.valid = true;
      this.value = new String[1];
      this.setDefaultValue();
      this.comment = "";
   }
   
   /**
    * @param name A unique variable name for this block.
    * @param owner Which JJClass it belongs to
    * @param ID A globally unique identifier
    * @param vType The type of variable this is
    * @param access The access keywords for this variable
    * @throws EmptyStringException
    * 
    * Creates a new VarBlock with everything specified. AccessTypes should only be set
    * if this is being used as a class variable.
    */
   public VarBlock(String name, JJClass owner, int ID, VarTypes vType,
		   AccessTypes[] access,  int length) 
      throws Exception
   {
      
      if(length < 1)
      {
    	  throw new BlockException("Can't create " + length + " length array");
      }
      this.setName(name);
      this.setOwner(owner);
      this.id = ID;
      this.type = BlockTypes.VAR;
      this.varType = vType;
      this.setAccess(access);
      this.refs = new LinkedList<RefBlock>();
      this.object = null;
      this.literal = false;
      this.length = length;
      this.valid = true;
      this.value = new String[length];
      this.setDefaultValue();
      this.comment = "";
   }
   
   public VarBlock clone(int id) throws Exception
   {
      VarBlock vb = new VarBlock(getName(), getOwner(), id, getVarType(), isLiteral());
      vb.setLength(getLength());
      vb.setAccess(getAccess());
      if(!isLiteral())
         for(int i = 0; i < getLength(); i++)
            vb.setValue(getValue(i), i);
      vb.setObjectType(getObjectType());
      vb.setComment(getComment());
      return vb;
   }
   
   ///Sets a default value for this variable based on type
   private void setDefaultValue()
   {
      String val;
      switch(this.varType)
      {
      case BYTE:
      case SHORT:
      case INT:
      case LONG:
      case FLOAT:
      case DOUBLE:
      case CHAR:
         val = "0";
         break;
      case BOOLEAN:
         val = "false";
         break;
      case OBJECT:
         val = null;
         break;
      default:
         val = null;
         break;
      }
      for(int i = 0; i < value.length; i++)
      {
         value[i] = val;
      }
   }
   
   ///returns whether or not this is a literal (value instead of variable)
   public boolean isLiteral()
   {
      return literal;
   }
   
   public void setValue(String value, int index)
   {
      this.value[index] = value;
   }

   public String getValue(int index)
   {
      return value[index];
   }
   
   public int getValueLength()
   {
      return value.length;
   }

   public void setObjectType(String objectType)
   {
      this.objectType = objectType;
   }

   public String getObjectType()
   {
      return objectType;
   }

   /**
    * \defgroup CallableMethods These methods are common to CodeBlock, BlockBlock, VarBlock, and ObjectBlock.
    */
   /*@{*/
   
   ///Returns the list of access keywords
   public AccessTypes[] getAccess()
   {
      return access;
   }
   
   /**
    * @param access
    * @throws BlockException
    * 
    * Sets the list of AccessTypes keywords. May be null.
    * This will also check to ensure access types aren't being set for an INLINE
    * style block and that only one of public/private/protected is set. It will
    * also set the appropriate boolean flags for quick access to access settings.
    */
   public void setAccess(AccessTypes[] access) throws Exception
   {
      AccessTypes[] tempAccess = this.access;
      boolean[] tempIsAccess = this.isAccess.clone();
      ///Set the access keywords
      this.access = access;
      ///Set the boolean array to false to start
      for(int i = 0; i < this.isAccess.length; i++)
      {
         this.isAccess[i] = false;
      }
      ///Now set true any access keywords we added 
      if(access != null)
      {
         for(int i = 0; i < access.length; i++)
         {
            this.isAccess[access[i].getIndex()] = true;
         }
         ///You can only have one of the three visibility access types
         if((this.isAccess[AccessTypes.PUBLIC.getIndex()] && this.isAccess[AccessTypes.PRIVATE.getIndex()])
            || (this.isAccess[AccessTypes.PUBLIC.getIndex()] && this.isAccess[AccessTypes.PROTECTED.getIndex()])
            || (this.isAccess[AccessTypes.PRIVATE.getIndex()] && this.isAccess[AccessTypes.PROTECTED.getIndex()]))
         {
            ///so if we improperly set one of these undo changes and throw an exception
            this.access = tempAccess;
            this.isAccess = tempIsAccess;
            throw new BlockException("Can only have one of the following access types public/private/protected.");
         }
      }
      return;
   }
 
   ///returns whether or not this has the public keyword
   public boolean isPublic()
   {
      return isAccess[AccessTypes.PUBLIC.getIndex()];
   }

   ///returns whether or not this has the private keyword
   public boolean isPrivate()
   {
      return isAccess[AccessTypes.PRIVATE.getIndex()];
   }

   ///returns whether or not this has the protected keyword
   public boolean isProtected()
   {
      return isAccess[AccessTypes.PROTECTED.getIndex()];
   }

   ///returns whether or not this has the static keyword
   public boolean isStatic()
   {
      return isAccess[AccessTypes.STATIC.getIndex()];
   }

   ///returns whether or not this has the final keyword
   public boolean isFinal()
   {
      return isAccess[AccessTypes.FINAL.getIndex()];
   }
   
   ///returns the Java friendly name used to call this variable
   public String getCallName()
   {
      return callName;
   }
   
   /*@}*/
   
   /**
    * \defgroup BlockMethods These methods are common to all blocks.
    */
   /*@{*/

   ///Sets the comment for this block
   public void setComment(String comment)
   {
      this.comment = comment;
      if(this.object != null)
         this.object.setComment(comment);
   }
   ///Gets the comment for this block
   public String getComment()
   {
      return comment;
   }
   
   ///Return whether this block is still valid or if it is deleted pending
   ///removal of all its refs.
   public boolean isValid()
   {
      return valid;
   }

   ///VarBlock doesn't own any references, so the only time it could have an
   ///error is if it's an object and the class doesn't exist anymore.
   public boolean hasError()
   {
      if(this.object != null) return this.object.hasError();
      return false;
   }
   
   ///Since it owns no refs it doesn't have anything to check for errors
   public void checkError()
   {
      return;
   }
   
   ///Sets this block to not Valid and updates all its refs
   public void deleteBlock()
   {
      valid = false;
      if(this.object != null) this.object.deleteBlock();
      this.updateRefs();
   }
   
   ///Sets this block to Valid and updates all its refs
   public void undeleteBlock()
   {
      valid = true;
      if(this.object != null) this.object.undeleteBlock();
      this.updateRefs();
   }
   
   ///This makes it easier for the garbage collector to remove this block
   ///by ensuring all the references it contains are removed.
   public void destroyBlock()
   {
      ///If this is an object variable destroy the object
      if(this.object != null)
         this.object.destroyBlock();
      ///set this block to invalid
      this.valid = false;
      ///clear all references to this block
      Iterator<RefBlock> it = this.refs.iterator();
      while(it.hasNext())
      {
         it.next().clearBlock();
      }
      this.refs.clear();
   }
   
   ///Accepts a BlockVisitorInterface and visits itself
   public BVReturn accept(BlockVisitorInterface bv)
   {
      bv.visit(this);
      return null;
   }
   
   ///returns the JJClass that owns this block
   
   public JJClass getOwner()
   {
      return classOwner;
   }

   ///returns the id for this block
   
   public int getID()
   {
      return id;
   }

   ///returns the name of this block
   
   public String getName()
   {
      return name;
   }

   ///returns the BlockTypes type of this block
   
   public BlockTypes getType()
   {
      return type;
   }
   
   ///sets the JJClass that owns this block
   
   public void setOwner(JJClass owner)
   {
      this.classOwner = owner;
      
   }

   ///sets the name of this block as well as its callName.
   
   public void setName(String name) throws EmptyStringException
   {
      ///If the name is of 0 length we've made a mistake and need to throw
      ///an exception.
      if(name.length() == 0) 
      {
         throw new EmptyStringException("Empty string name");
      }
      this.name = name;
      ///The callname should be the same as the name with some changes to make it
      ///Java friendly
      this.callName = name.replaceAll("[^a-z&&[^A-Z]&&[^0-9]]+", "_");
      if(!(this.callName.substring(0, 1).matches("[a-zA-z]")))
      {
         this.callName = "a" + this.callName;
      }
   }
   
   ///Adds a RefBlock to the list of references
   
   public void addRef(RefBlock ref)
   {
      if(refs.contains(ref)) return;
      refs.add(ref);
   }

   ///Removes the given RefBlock from the list of references
   
   public void remRef(RefBlock ref)
   {
      refs.remove(ref);
   }

   ///Updates all the RefBlock references to this block
   
   public void updateRefs()
   {
      Iterator<RefBlock> it = refs.iterator();
      while(it.hasNext())
      {
         it.next().update();
      }
   }
   
   /*@}*/
   
   ///Returns the type of variable this is
   public VarTypes getVarType()
   {
      return varType;
   }
   
   ///Returns the length (size of declared array) of the variable
   public int getLength()
   {
      return length;
   }
   
   ///Sets the length(size of declared array) for this variable
   public void setLength(int length)
      throws BlockException
   {
      if(length < 1)
      {
         throw new BlockException("Cannot create array of length " + length);
      }
      this.length = length;
      this.value = new String[length];
      this.setDefaultValue();
   }
   
   ///Sets the ObjectBlock associated with this variable
   public void setObject(ObjectBlock object)
   {
      this.object = object;
   }

   ///Returns the ObjectBlock associated with this variable
   public ObjectBlock getObject()
   {
      return object;
   }

   ///Converts the VarBlock to a readable String and returns it.
   public String toString()
   {
      String s = "Type:" + type.toString();
      s += " VarType:" + varType.toString();
      s += " ID:" + id;
      s += " Name:" + name;
      s += " CallName:" + callName;
      s += " Length:[" + length + "]";
      s += " Owner:" + classOwner;
      s += " Access:";
      if(access != null)
         for(int i = 0; i < access.length; i++)
            s += access[i].toString() + " ";
      s+= " NumRefs:" + refs.size();
      
      return s;
   }
   
   ///Test function for VarBlock. Generates a couple variables and then prints
   ///out their contents.
   public static void main(String[] args)
   {
      VarBlock vb1;
      VarBlock vb2;
      try
      {
         vb1 = new VarBlock("VB1", null, 1, VarTypes.INT);
         vb2 = new VarBlock("VB2", null, 2, VarTypes.BOOLEAN, null, 4);
      }
      catch(Exception e)
      {
         System.err.println(e.getMessage());
         return;
      }
      System.out.println(vb1 + "\n" + vb2);
      
   }

}
